#include <fcntl.h>
#include <errno.h>
#include <http.h>
#include <snprintf.h>


int SSLCtx::initialize(void)
{
    SSL_load_error_strings();
    SSL_library_init();
    return 0;
}


SSLCtx::SSLCtx(void)
{
    ctx=0;
    *passwd=0;
}
SSLCtx::~SSLCtx(void)
{
    destroy();
}
int SSLCtx::pem_passwd_cb(char* buf,int size,int rwflag,void* data)
{
    int rc=__snprintf(buf,size,"%s",((SSLCtx*)data)->passwd);
    return rc>=size?size-1:rc;
}
int SSLCtx::create(SSL_METHOD* method)
{
    ctx=SSL_CTX_new(method);
    if(!ctx)
	return -1;
    SSL_CTX_set_default_passwd_cb(ctx,SSLCtx::pem_passwd_cb);
    SSL_CTX_set_default_passwd_cb_userdata(ctx,this);
    return 0;
}
int SSLCtx::destroy(void)
{
    if(ctx)
    {
	SSL_CTX_free(ctx);
	ctx=0;
    }
    return 0;
}
int SSLCtx::set_cipher(const char* cipher)
{
    if(!ctx)
	return -1;
    return SSL_CTX_set_cipher_list(ctx,cipher)>0?0:-1;
}
SSL_CTX* SSLCtx::get_context(void)
{
    return ctx;
}
int SSLCtx::set_certificate(const char* certfile,const char* keyfile,const char* pass)
{
    if(!ctx)
	return -1;
    if(!certfile || !keyfile)
	return -1;
    if(!pass)
	pass="";
    __snprintf(passwd,SSL_MAX_PASSWD_SIZE,pass);
    if(!SSL_CTX_use_certificate_file(ctx,certfile,SSL_FILETYPE_PEM))
	return -1;
    if(!SSL_CTX_use_RSAPrivateKey_file(ctx,keyfile,SSL_FILETYPE_PEM))
	return -1;
    return 0;
}






Socket::Socket(void)
{
    fd=INVALID_SOCKET;
    ssl=0;
    ctimeout=0;
    rtimeout=0;
    wtimeout=0;
    secure=0;
    *inbuf=0;
    inoffset=0;
    insize=0;
    *outbuf=0;
    outsize=0;
    ssl_ctx=0;
}
Socket::~Socket(void)
{
    close();
}
void Socket::attach(SSLCtx* ctx)
{
    ssl_ctx=ctx;
}
void Socket::set_timeouts(int c,int r,int w)
{
    ctimeout=c;
    rtimeout=r;
    wtimeout=w;
}			    

int Socket::connect(const char* host)
{
    char addr[1024];
    __snprintf(addr,sizeof(addr),host);
    char* port=strchr(addr,':');
    if(!port)
	return -1;
    *port=0;
    port++;

    sockaddr_in sin;
    sin.sin_family=AF_INET;
    sin.sin_port=htons(atol(port));
    sin.sin_addr.s_addr=inet_addr(addr);

    if(!sin.sin_port)
    {
	servent* se=getservbyname(port,"tcp");
	if(!se)
	    return -1;
	sin.sin_port=se->s_port;
    }

    if(sin.sin_addr.s_addr==INADDR_NONE)
    {
        hostent* he=gethostbyname(addr);
	if(!he)
	    return -1;
	memcpy((char*)&sin.sin_addr.s_addr,he->h_addr,sizeof(sin.sin_addr.s_addr));
    }

    fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd!=INVALID_SOCKET)
    {
    	u_long on=1;
	setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(const char*)&on,sizeof(on));

//	if(!ioctlsocket(fd,FIONBIO,&on))
	if (!fcntl(fd, F_SETFL, O_NONBLOCK))
	{
		int rc=::connect(fd,(sockaddr*)&sin,sizeof(sockaddr_in));
		if(!rc)
			return 0;
//		if(WSAGetLastError()==WSAEINPROGRESS || WSAGetLastError()==WSAEWOULDBLOCK)
		if(errno == EINPROGRESS || errno == EWOULDBLOCK)
		{
			fd_set rd;
			fd_set wr;
			fd_set ex;
			FD_ZERO(&rd);
			FD_ZERO(&wr);
			FD_ZERO(&ex);
			FD_SET(fd,&rd);
			FD_SET(fd,&wr);
			FD_SET(fd,&ex);
			timeval tv;
			tv.tv_sec=ctimeout;
			tv.tv_usec=0;
			if(select(fd+1,&rd,&wr,&ex,ctimeout>0?&tv:0)>0)
			{
				if((FD_ISSET(fd,&rd) || FD_ISSET(fd,&wr)) && !FD_ISSET(fd,&ex))
					return 0;
			}
		}
		shutdown(fd,2);
	}
	::closesocket(fd);
	fd=INVALID_SOCKET;
    }
    return -1;
}
int Socket::disconnect(void)
{
    if(fd!=INVALID_SOCKET)
    {
	shutdown(fd,2);
	::closesocket(fd);
	fd=INVALID_SOCKET;
    }
    return 0;
}

int Socket::sslconnect(void)
{
    fd_set fdset;
    timeval tv;
    int rc;

    for(;;)
    {
	rc=SSL_connect(ssl);
	if(!rc)
	    break;
	if(rc>0)
	    return 0;
	rc=SSL_get_error(ssl,rc);
	if(rc==SSL_ERROR_WANT_READ)
	{
	    FD_ZERO(&fdset);
	    FD_SET(fd,&fdset);
	    tv.tv_sec=ctimeout;
	    tv.tv_usec=0;
	    rc=select(fd+1,&fdset,0,0,ctimeout>0?&tv:0);
	    if(rc<=0 && !FD_ISSET(fd,&fdset))
		break;
	}else if(rc==SSL_ERROR_WANT_WRITE)
	{
	    FD_ZERO(&fdset);
	    FD_SET(fd,&fdset);
	    tv.tv_sec=ctimeout;
	    tv.tv_usec=0;
	    rc=select(fd+1,0,&fdset,0,ctimeout>0?&tv:0);
	    if(rc<=0 && !FD_ISSET(fd,&fdset))
		break;
	}else break;
    }
    return -1;
}

void Socket::reset(void)
{
    inoffset=0;
    insize=0;
    outsize=0;
}
int Socket::flush(void)
{
    if(outsize>0)
    {
        int rc=directwrite(outbuf,outsize);
        outsize=0;
        return rc;
    }
    return 0;
}

int Socket::set_secure(void)
{
	if(secure)
		return 0;
	secure=1;
	flush();
	if(!ssl_ctx)
		return -1;
	ssl=SSL_new(ssl_ctx->get_context());
	if(ssl)
	{
		if(SSL_set_fd(ssl,fd))
		{
			if(!sslconnect())
				return 0;
			SSL_shutdown(ssl);
		}
		disconnect();
		SSL_free(ssl);
		ssl=0;
	}
	return -1;
}

int Socket::open(const char* host,int sec)
{
    close();
    reset();
    secure=sec;
    if(secure)
    {
	if(!ssl_ctx)
	    return -1;
	ssl=SSL_new(ssl_ctx->get_context());
	if(ssl)
	{
	    if(!connect(host))
	    {
		if(SSL_set_fd(ssl,fd))
		{
		    if(!sslconnect())
			return 0;
		    SSL_shutdown(ssl);
		}
		disconnect();
	    }
	    SSL_free(ssl);
	    ssl=0;
	}
    }else
    {
	if(!connect(host))
	    return 0;
    }
    return -1;
}
int Socket::close(void)
{
    flush();
    if(ssl)
    {
	SSL_shutdown(ssl);
	SSL_free(ssl);
	ssl=0;
    }
    disconnect();
    return 0;
}
int Socket::sslwrite(const char* buf,int size)
{
    fd_set fdset;
    timeval tv;
    int rc;
    for(;;)
    {
	rc=SSL_write(ssl,buf,size);
	if(rc>0)
	    return rc;
	rc=SSL_get_error(ssl,rc);
	if(rc==SSL_ERROR_WANT_READ)
	{
	    FD_ZERO(&fdset);
	    FD_SET(fd,&fdset);
	    tv.tv_sec=wtimeout;
	    tv.tv_usec=0;
	    rc=select(fd+1,&fdset,0,0,wtimeout>0?&tv:0);
	    if(rc<=0 && !FD_ISSET(fd,&fdset))
		return -1;
	}else if(rc==SSL_ERROR_WANT_WRITE)
	{
	    FD_ZERO(&fdset);
	    FD_SET(fd,&fdset);
	    tv.tv_sec=wtimeout;
	    tv.tv_usec=0;
	    rc=select(fd+1,0,&fdset,0,wtimeout>0?&tv:0);
	    if(rc<=0 && !FD_ISSET(fd,&fdset))
		return -1;
	}else break;
    }
    return 0;
}
int Socket::sslread(char* buf,int size)
{
    fd_set fdset;
    timeval tv;
    int rc;
    for(;;)
    {
	rc=SSL_read(ssl,buf,size);
	if(rc>0)
	    return rc;
	rc=SSL_get_error(ssl,rc);
	if(rc==SSL_ERROR_WANT_READ)
	{
	    FD_ZERO(&fdset);
	    FD_SET(fd,&fdset);
	    tv.tv_sec=rtimeout;
	    tv.tv_usec=0;
	    rc=select(fd+1,&fdset,0,0,rtimeout>0?&tv:0);
	    if(rc<=0 && !FD_ISSET(fd,&fdset))
		return -1;
	}else if(rc==SSL_ERROR_WANT_WRITE)
	{
	    FD_ZERO(&fdset);
	    FD_SET(fd,&fdset);
	    tv.tv_sec=rtimeout;
	    tv.tv_usec=0;
	    rc=select(fd+1,0,&fdset,0,rtimeout>0?&tv:0);
	    if(rc<=0 && !FD_ISSET(fd,&fdset))
		return -1;
	}else break;
    }
    return 0;
}

int Socket::fdread(char* buf,int size)
{
    fd_set rd;
    FD_ZERO(&rd);
    FD_SET(fd,&rd);
    timeval tv;
    tv.tv_sec=rtimeout;
    tv.tv_usec=0;
    
    int rc=select(fd+1,&rd,0,0,rtimeout>0?&tv:0);
    if(rc<=0 || !FD_ISSET(fd,&rd)) return -1;

    rc=recv(fd,buf,size,0);
    if(rc<=0)
	return 0;
    return rc;
}
int Socket::fdwrite(const char* buf,int size)
{
    fd_set wr;
    FD_ZERO(&wr);
    FD_SET(fd,&wr);
    timeval tv;
    tv.tv_sec=wtimeout;
    tv.tv_usec=0;

    int rc=select(fd+1,0,&wr,0,wtimeout>0?&tv:0);
    if(rc<=0 || !FD_ISSET(fd,&wr)) return -1;

    rc=send(fd,buf,size,0);
    if(rc<=0)
	return 0;
    return rc;
}
int Socket::directread(char* buf,int size)
{
    if(secure)
	return sslread(buf,size);
    return fdread(buf,size);
}
int Socket::directwrite(const char* buf,int size)
{
    if(secure)
	return sslwrite(buf,size);
    return fdwrite(buf,size);
}

int Socket::_getchar(void)
{
    if(inoffset>=insize)
    {
	int rc=directread(inbuf,SOCKET_BUFFER_SIZE);
	if(rc<0)
	    return -1;
	insize=rc;
	if(!insize)
	    return 0;
	inoffset=0;
    }
    return ((unsigned char*)inbuf)[inoffset++];
}
int Socket::read(char* dst,int len)
{
    int n=0;
    if(inoffset<insize)
    {
	n=insize-inoffset;
	if(len<n) n=len;
	memcpy(dst,inbuf+inoffset,n);
	inoffset+=n;
    }else
	n=directread(dst,len);
    return n;
}
int Socket::readln(char* dst,int len)
{
    len--;
    int n=0;
    while(n<len)
    {
	int ch=_getchar();
	if(ch<0)
	    return -1;
	if(!ch || ch=='\n')
	    break;
	if(ch=='\r')
	    continue;
	if(ch=='\b')
	{
	    if(n>0)
		n--;
	    continue;
	}
	dst[n++]=(char)ch;
    }
    dst[n]=0;
    return n;
}

int Socket::_putchar(int ch)
{
    char c=(char)ch;
    return write(&c,1);
}
int Socket::dwrite(const char* src,int len)
{
    flush();
    return directwrite(src,len);
}
int Socket::write(const char* src,int len)
{
    int rc=0;
    while(len>0)
    {
	int n=SOCKET_BUFFER_SIZE-outsize;
	if(n>0)
	{
	    if(len<n)
		n=len;
	    memcpy(outbuf+outsize,src,n);
	    src+=n;
	    len-=n;
	    outsize+=n;
	    rc+=n;
	}
	if(outsize>=SOCKET_BUFFER_SIZE)
	    flush();
    }
    return rc;
}
int Socket::writeln(const char* src,int len)
{
    int rc=write(src,len);
    rc+=write("\r\n",2);
    return rc;
}
int Socket::printf(const char* fmt,...)
{
    va_list ap;
    va_start(ap,fmt);
    int rc=vprintf(fmt,ap);
    va_end(ap);
    return rc;
}
int Socket::vprintf(const char* fmt,va_list ap)
{
    char temp[SOCKET_BUFFER_SIZE];
    int rc=__vsnprintf(temp,sizeof(temp),fmt,ap);
    return write(temp,rc>=(int)sizeof(temp)?(int)sizeof(temp)-1:rc);
}













char* Socket::trim_buf(char* buf,int size)
{
    if(!buf) return 0;
    if(size<0)
	size=strlen(buf);
    if(!size)
	return buf;
    char* ptr;
    for(ptr=buf+size-1;ptr>=buf && *ptr==' ';ptr--);
    if(ptr[1]==' ') ptr[1]=0;
    for(ptr=buf;*ptr && *ptr==' ';ptr++);
    return ptr;
}
char* Socket::get_next_part(char* buf,char delimiter)
{
    if(!buf) return 0;
    char* ptr=strchr(buf,delimiter);
    if(!ptr)
	return 0;
    *ptr=0;
    ptr++;
    while(*ptr==delimiter) ptr++;
    return ptr;
}

int Socket::http_send_request(LPHTTPANSWER result,const char* method,const char* resource,const char* contenttype,const char* content,int length,
    const char* useragent,const char* host,int keepalive,const char* proxyauthtype,const char* proxyauth,int waitcontent)
{
    int rc;
    if(!result)
	return HTTP_ERR_ARGS;
    memset((char*)result,0,sizeof(HTTPANSWER));
    if(!resource || !*resource)
	resource="/";
    if(length<=0)
	content=0;

// send request
    printf("%s %s HTTP/1.0\r\n",(method && *method)?method:(content?"POST":"GET"),resource);
    if(proxyauthtype && *proxyauthtype && proxyauth && *proxyauth)
	printf("Proxy-Authorization: %s %s\r\n",proxyauthtype,proxyauth);
    if(host && *host)
	printf("Host: %s\r\n",host);
    if(useragent && *useragent)
	printf("User-Agent: %s\r\n",useragent);
    if(contenttype && *contenttype)
	printf("Content-Type: %s\r\n",contenttype);
    if(content)
	printf("Content-Length: %i\r\n",length);
    printf("Cache-Control: no-cache\r\nConnection: %s\r\n\r\n",keepalive?"Keep-Alive":"close");
    if(flush()<=0)
	return HTTP_ERR_WRITE;
    if(content)
    {
	int size=0;
	while(size<length)
	{
	    rc=dwrite(content+size,length-size);
	    if(rc<=0)
		break;
	    size+=rc;
	}
	if(size!=length)
	    return HTTP_ERR_WRITE;
    }
    
// recv status
    char temp[2048];
    rc=readln(temp,sizeof(temp));
    if(rc<=0)
	return HTTP_ERR_READ;
    char* ver=trim_buf(temp,rc);
    char* code=get_next_part(ver,' ');
    if(!code)
	return HTTP_ERR_FORMAT;
    char* message=get_next_part(code,' ');
    if(!message)
	return HTTP_ERR_FORMAT;
    if(lstrcmpi(ver,"HTTP/1.1") && lstrcmpi(ver,"HTTP/1.0"))
	return HTTP_ERR_FORMAT;
    result->result=atoi(code);

// recv headers
    length=-1;
    LPHTTPHDR begin=0;
    LPHTTPHDR end=0;
    while((rc=readln(temp,sizeof(temp)))>0)
    {
	char* ptr=strchr(temp,':');
	if(ptr)
	{
	    *ptr=0;
	    ptr++;
	    char* name=trim_buf(temp,-1);
	    char* value=trim_buf(ptr,-1);
	    if(*name || *value)
	    {
		if(!lstrcmpi(name,"Content-Length"))
		    length=atoi(value);

		int nname=strlen(name);
		int nvalue=strlen(value);
		
		ptr=new char[sizeof(HTTPHDR)+nname+nvalue+2];
		if(ptr)
		{
		    LPHTTPHDR hdr=(LPHTTPHDR)ptr;
		    hdr->nname=nname;
		    hdr->nvalue=nvalue;
		    hdr->next=0;
		    hdr->name=(char*)(hdr+1);
		    hdr->value=hdr->name+hdr->nname+1;
		    strcpy(hdr->name,name);
		    strcpy(hdr->value,value);
		    
		    if(!begin)
			begin=end=hdr;
		    else
		    {
			end->next=hdr;
			end=hdr;
		    }
		}
	    }
	}
    }
    if(rc<0)
    {
	http_free_answer(result);
	return HTTP_ERR_READ;
    }
    result->header=begin;

    if(result->result!=200)
	return HTTP_ERR_SUCCESS;
	
// recv data
    if(length>0)
    {
	char* data=new char[length+1];
	if(!data)
	{
	    http_free_answer(result);
	    return HTTP_ERR_OUTOFMEM;
	}
	
	int size=0;
	while(size<length)
	{
	    rc=read(data+size,length-size);
	    if(rc<=0)
		break;
	    size+=rc;
	}
	if(size!=length)
	{
	    delete[] data;
	    http_free_answer(result);
	    return HTTP_ERR_READ;
	}
	data[length]=0;
	result->content=data;
	result->length=length;
    }else if(length<0 && !keepalive && waitcontent)
    {
#define PAGE_SIZE 524288
	char* ptr=0;
	int size=0;
	int offset=0;
    
	char temp[8192];
	for(;;)
	{
		int rc=read(temp,sizeof(temp));
		if(rc<=0)
	 		break;
		if(!ptr || size-offset<rc)
		{
			char* p=(char*)realloc((char*)ptr,size+PAGE_SIZE);
			if(!p)
			{
				if(ptr)
					free(ptr);
				http_free_answer(result);
				return HTTP_ERR_OUTOFMEM;
			}
			ptr=p;
			size+=PAGE_SIZE;
		}
		memcpy(ptr+offset,temp,rc);
		offset+=rc;
	}
	if(ptr && offset>0)
	{
		result->content=new char[offset+1];
		if(!result->content)
		{
			free(ptr);
			http_free_answer(result);
			return HTTP_ERR_OUTOFMEM;
		}
		result->length=offset;
		memcpy(result->content,ptr,offset);
		result->content[offset]=0;
		free(ptr);
	}
#undef PAGE_SIZE
    }
    return HTTP_ERR_SUCCESS;
}


int Socket::http_free_answer(LPHTTPANSWER result)
{
    if(!result)
	return -1;
    if(result->content)
	delete[] result->content;
    while(result->header)
    {
	LPHTTPHDR tmp=result->header;
	result->header=result->header->next;
	delete tmp;
    }
    memset((char*)result,0,sizeof(HTTPANSWER));
    return 0;
}
